package sim.core;

import java.awt.*;
import java.util.*;

/**
 * The main part of the simulation engine. The Scheduler manages a priority
 * queue of task, issuing tasks when simulation time reaches their time of
 * execution.
 * 
 * New tasks and re-scheduled tasks are inserted into the priority queue and
 * sorted according to the task execution deadlines. In each iteration, the
 * scheduler picks the next task to execute and advances simulation time
 * accordingly. Therefore, simulation time advances in discrete steps and is
 * independent of real time. Depending on the number of tasks in the queue and
 * the time spent when they are executed, simulation time can be faster or
 * slower than real time.
 * 
 * @author smd
 * 
 */
public class Scheduler implements Displayable {

	private static double now = 0;
	private static boolean stopped = false;
	public static Scheduler Scheduler = new Scheduler(); // dummy instance...

	/**
	 * Returns the number of simulation seconds that elapsed since the
	 * simulation started.
	 * 
	 * @return The number of simulation seconds that elapsed since the
	 *         simulation started.
	 */
	static double now() {
		return now;
	}

	/**
	 * Cancels all the tasks in the scheduler queue, effectively ending the
	 * simulation. Typically used by an external observer task when it detects
	 * that a certain global condition has been reached, all nodes infected for
	 * example...
	 */
	public static void cancelAll() {
		for (Task i : queue)
			i.cancel();
		stopped = true;
	}

	/**
	 * Cancels all the tasks in the scheduler queue of a given owner. Typically
	 * used when a node goes offline.
	 * 
	 * @param owner
	 *            The owner of the tasks to be canceled.
	 */
	public static void cancelAll(Object owner) {
		for (Task i : queue)
			if (i.owner == owner)
				i.cancel();
	}

	static void deleteTask(Task t) {
		queue.remove(t);
		t.isQueued = false;
	}

	static Task schedule(Task t, double due) {
		t.due = now + Math.max(1e-9, due);
		queue.add(t);
		t.isQueued = true;
		return t;
	}

	static void reSchedule(Task t, double due) {
		if (t.isQueued)
			queue.remove(t) ;
		t.due = now_rt() + Math.max(1e-9, due);
		queue.add(t);
		t.isQueued = true;
	}

	static int N = 0;

	static void run() {

		new Task(Double.MAX_VALUE) {
			public void run() {
				System.err.println("Scheduling queue is empty...");
			}
		};
		while( !queue.isEmpty() && !stopped)
			processTasks();
	}

	static void processTasks() {

		Task task = queue.remove();
		if (task == null || task.isCancelled)
			return;

		now = task.due;
		rt1 = System.nanoTime();
		try {
			task.reset();
			task.run();
			task.reSchedule();
		} catch (Exception x) {
			System.err.println("Offending task cancelled...");
			x.printStackTrace();
		}

		synchronized (performed) {
			performed.put(task, 0);
		}
	}

	private static double now_rt() {
		return now + (System.nanoTime() - rt1) * 1e-9;
	}

	/**
	 * Starts the simulator and begins executing tasks in order.
	 */
	static public void start() {
		run();
	}

	private static double rt1, rt0 = System.nanoTime();
	private static PriorityQueue<Task> queue = new PriorityQueue<Task>();
	private static WeakHashMap<Task, Integer> performed = new WeakHashMap<Task, Integer>();

	public void display(Graphics2D gu, Graphics2D gs) {

		double rt = (System.nanoTime() - rt0) * 1e-9;
		String time = String.format("Time:%.1f/%.1f(%.1fx) : %d", now, rt, now
				/ rt, queue.size());

		gu.setColor(Color.black);
		gu.drawString(time, 1, 20);

		gs.setColor(Color.black);
		gs.setStroke(new BasicStroke(1f));

		synchronized (performed) {
			for (Map.Entry<Task, Integer> i : performed.entrySet()) {
				Task t = i.getKey();
				if (t != null) {
					gs.setColor(Color.lightGray);
					t.display(gu, gs);
				}
			}
			performed.clear();
		}
	}
}
